package com.zhou.piledriver.utils;

import com.zhou.piledriver.model.dto.DifferenceChildDto1;
import com.zhou.piledriver.model.dto.DifferenceChildDto2;
import com.zhou.piledriver.model.dto.DifferenceDto1;
import com.zhou.piledriver.model.dto.DifferenceDto2;
import lombok.extern.slf4j.Slf4j;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author ZhouShenBin
 * @date 2024/1/26 11:25
 * @description
 */

@Slf4j
public class Test {

    public static void main(String[] args) {
        DifferenceDto1 differenceDto1 = new DifferenceDto1();
        differenceDto1.setId(77651L);
        differenceDto1.setAge(20);
        differenceDto1.setName("张三");
        differenceDto1.setRoleName("管理员");
        DifferenceChildDto1 dc1 = new DifferenceChildDto1();
        dc1.setId(778L);
        dc1.setType("type1");
        dc1.setLength(10);
        differenceDto1.setChild(dc1);

        DifferenceDto2 differenceDto2 = new DifferenceDto2();
        differenceDto2.setId(77652L);
        differenceDto2.setAge(21);
        differenceDto2.setName("李四");
        differenceDto2.setSex("女");
        DifferenceChildDto2 dc2 = new DifferenceChildDto2();
        dc2.setId(999L);
        dc2.setValue("value");
        dc2.setLength(20);
        differenceDto2.setChild(dc2);

        log.info(differenceDto1.toString());
        log.info(differenceDto2.toString());

        diff(differenceDto1,differenceDto2);
    }

    public static <T> void diff(T before, T after, Class<?>... customerClass) {
        //can not be null for all
        if (before == null && after == null) {
            return;
        }
        //get all fields for none null
        T allFieldsObj = after;
        List<Field> finalFields = getClassAllFields(allFieldsObj);
        log.info(finalFields.toString());
        //iterator field
        for (Field declaredField : finalFields) {
            getDiffInfo(before, after, declaredField, customerClass);
        }

    }

    private static List<Field> getClassAllFields(Object obj) {
        Field[] superFields = null;
        Class<?> superclass = obj.getClass().getSuperclass();
        //get supper declared fields
        if (superclass != null) {
            superFields = superclass.getDeclaredFields();
        }
        //get declared fields
        Field[] fields = obj.getClass().getDeclaredFields();
        //all fields
        Field[] finalFields;
        if (superFields != null && superFields.length > 0) {
            finalFields = new Field[superFields.length + fields.length];
            //copy all super field
            System.arraycopy(superFields, 0, finalFields, 0, superFields.length);
            //copy all self field
            System.arraycopy(fields, 0, finalFields, superFields.length, fields.length);
        } else {
            finalFields = fields;
        }
        return Arrays.stream(finalFields).collect(Collectors.toList());
//        return Arrays.stream(finalFields).filter(field -> field.isAnnotationPresent()).collect(Collectors.toList());
    }

    private static <T> void getDiffInfo(T before, T after, Field declaredField, Class<?>... customerClass) {
        final String uId = "serialVersionUID";
        //ignore serialVersionUID
        if (uId.equals(declaredField.getName())) {
            return;
        }
        Object beforeInvoke = null;
        Object afterInvoke = null;
        if (before != null) {
            try {
                //get diff before object by invoke
                beforeInvoke = new PropertyDescriptor(declaredField.getName(), before.getClass()).getReadMethod().invoke(before);
            } catch (Exception ignored) {
            }
        }

        if (after != null) {
            try {
                //get diff after object by invoke
                afterInvoke = new PropertyDescriptor(declaredField.getName(), after.getClass()).getReadMethod().invoke(after);
            } catch (Exception ignored) {
            }
        }

        Class<?> classType = declaredField.getType();
        String classTypeName = classType.getName();
        //adding diffs
        addingDiff(declaredField, beforeInvoke, afterInvoke, customerClass);
        if (beforeInvoke != null && afterInvoke != null) {
            if (beforeInvoke instanceof ArrayList) {
                List<?> beforeList = (ArrayList<?>) beforeInvoke;
                List<?> afterList = (ArrayList<?>) afterInvoke;
                //break when size not equals
                if (afterList.size() != beforeList.size()) {
                    return;
                }
                int index = 0;
                for (Object beforeObj : beforeList) {
                    //avoid recursive reference
                    if (beforeObj.getClass() == before.getClass()) {
                        throw new RuntimeException("Can not recursive depend");
                    }
                    //recursive call
                    diff(beforeObj, afterList.get(index), customerClass);
                    index++;
                }
            }
        }
    }

    private static <T> void addingDiff(Field declaredField, T beforeInvoke, T afterInvoke, Class<?>... customerClass) {
        //get field annotation

        //adding string diff
        if (String.class.getName().equals(declaredField.getType().getName())) {
            String beforeField = beforeInvoke == null ? "" : (String) beforeInvoke;
            String afterField = afterInvoke == null ? "" : (String) afterInvoke;
            if (!beforeField.equals(afterField)) {
                log.info(declaredField.getName()+":" +beforeField + "->" + afterField);
            }

        }
        //adding integer diff
        if (Integer.class.getName().equals(declaredField.getType().getName())) {
            Integer beforeField = beforeInvoke == null ? 0 : (Integer) beforeInvoke;
            Integer afterField = afterInvoke == null ? 0 : (Integer) afterInvoke;
            if (!beforeField.equals(afterField)) {
                log.info(declaredField.getName()+":" +beforeField + "->" + afterField);
            }

        }
        //adding long diff
        if (Long.class.getName().equals(declaredField.getType().getName())) {
            Long beforeField = Objects.isNull(beforeInvoke) ? 0 : (Long) beforeInvoke;
            Long afterField = Objects.isNull(afterInvoke) ? 0 : (Long) afterInvoke;
            if (!Objects.equals(beforeField, afterField)) {
                log.info(declaredField.getName()+":" +beforeField + "->" + afterField);
            }
        }

        //adding customer class diff
        if (customerClass != null && customerClass.length > 0) {
            List<String> customerClassNames = Arrays.stream(customerClass).map(Class::getName).collect(Collectors.toList());
            if (customerClassNames.stream().anyMatch(declaredField.getType().getName()::equals)) {
                //recursive call
                diff(beforeInvoke, afterInvoke, customerClass);
            }
        }
    }

    private static String[] getFiledName(Object o){
        Field[] fields=o.getClass().getDeclaredFields();
        String[] fieldNames=new String[fields.length];
        for(int i=0;i<fields.length;i++){
            fieldNames[i]=fields[i].getName();
        }
        return fieldNames;
    }
}
